package com.bluedot.www.framework.mvc.servlet.http;

import com.bluedot.www.framework.mvc.utils.Assert;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author He Peng
 * @version 1.0
 * @date 2021/8/22 16:33
 */
public class MultipartFileResolver {

    private static final String TEMP_DIR_CONTEXT_ATTRIBUTE = "javax.servlet.context.tempdir";
    private static final Integer MAX_SIZE = 1024 * 1024 * 1024;
    /**
     * 缓冲区大小，默认为1M
     */
    private Integer sizeThreshold = 1024 * 1024;
    /**
     * 单个文件，默认为10M
     */
    private Integer fileSizeMax = 1024 * 1024 * 10;
    /**
     * 所有文件，默认为10M
     */
    private Integer sizeMax = 1024 * 1024 * 10;


    public MultipartFileResolver() {
    }

    public MultipartFileResolver(Integer sizeThreshold, Integer fileSizeMax, Integer sizeMax) {
        this.sizeThreshold = sizeThreshold;
        this.fileSizeMax = fileSizeMax;
        this.sizeMax = sizeMax;
    }

    /**
     * 保存上传的文件至在WEB-INF目录下
     *
     * @param req:          http请求
     * @param relativePath: 相对路径
     * @author He Peng
     * @date 2021/8/23 15:51
     */
    public static void fileUpload(HttpServletRequest req, String relativePath) {
        //判断上传的表单是普通表单还是带文件的表单，是返回true,否返回false；
        if (!ServletFileUpload.isMultipartContent(req)) {
            //如果这是一个普通文件我们直接返回
            return;
        }

        String uploadPath = (getWebInfPath() + relativePath).replaceAll("\\+", "\\");
        uploadPath = uploadPath.replace("/", File.separator);

        File uploadFile = new File(uploadPath);
        //如果目录不存在就创建这样一个目录
        if (!uploadFile.exists()) {
            uploadFile.mkdir();
        }

        // 临时路径，如果上传的文件超过预期的大小，我们将它存放到一个临时目录中
        // 此临时路径由servlet容器提供
        File file = getTmpDir(req.getServletContext());


        try {
            //1、创建DiskFileItemFactory对象，处理文件上传路径或限制文件大小
            DiskFileItemFactory factory = gteDiskFileItemFactory(file);

            //2、获取ServletFileUpload
            ServletFileUpload upload = getServletFileUpload(factory);

            //3、处理上传文件，保存文件到服务器
            uploadParseRequest(upload, req, uploadPath);

        } catch (FileUploadException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 创建DiskFileItemFactory对象，处理文件上传路径或限制文件大小
     */
    public static DiskFileItemFactory gteDiskFileItemFactory(File file) {
        //1、创建DiskFileItemFactory对象，处理文件上传路径或限制文件大小
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //通过这个工厂设置一个缓冲区，当上传的文件大小大于缓冲区的时候，将它放到临时文件中；
        //缓冲区大小为1M
        factory.setSizeThreshold(1024 * 1024);
        factory.setRepository(file);
        return factory;
    }


    /**
     * 获取ServletFileUpload
     *
     * @param factory: FileItemFactory 工厂的实现类 创建了FileItem
     * @return org.apache.commons.fileupload.servlet.ServletFileUpload
     * @author He Peng
     * @date 2021/8/22 16:45
     */
    public static ServletFileUpload getServletFileUpload(DiskFileItemFactory factory) {

        ServletFileUpload upload = new ServletFileUpload(factory);
        // 监听文件上传进度
        upload.setProgressListener((pBytesRead, lpContentLength, i) -> {
            //pBytesRead:已读取到的文件大小
            //pContentLenght：文件大小
            float percentage = lpContentLength / pBytesRead;
            System.out.println("总大小：" + lpContentLength + " 已完成：" + percentage + "%");
        });

        //处理乱码问题
        upload.setHeaderEncoding("UTF-8");
        //设置单个文件的最大值
        upload.setFileSizeMax(1024 * 1024 * 10);
        //设置总共能够上传文件的大小
        //1024 = 1kb * 1024 = 1M * 10 = 10M
        upload.setSizeMax(1024 * 1024 * 10);
        return upload;
    }


    /**
     * 保存文件至服务器
     *
     * @param upload:
     * @param request:
     * @param uploadPath:
     * @return boolean
     * @author He Peng
     * @date 2021/8/23 10:54
     */
    private static boolean uploadParseRequest(ServletFileUpload upload, HttpServletRequest request, String uploadPath) throws Exception {

        //把前端的请求解析，封装成一个FileItem对象
        List<FileItem> fileItems = upload.parseRequest(request);
        //遍历每一个上传的文件
        for (FileItem fileItem : fileItems) {
            //判断是普通表单还是带文件的表单
            if (fileItem.isFormField()) {
                //getFieldName指的是前端表单控件的name
                String name = fileItem.getFieldName();
                //处理乱码
                String value = fileItem.getString("UTF-8");
                System.out.println(name + ":" + value);
            } else {
                //判断它是带文件的表单
                //======================处理文件=======================//

                //拿到文件的名字
                String uploadFileName = fileItem.getName();
                System.out.println("======>上传的文件名：" + uploadFileName);

                if (uploadFileName == null || "".equals(uploadFileName.trim())) {
                    continue;
                }

                //获得上传的文件名，例如/img/girl/ooa.jpg,只需要ooa，其前面的后面的都不需要
                String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);

                //获得文件的后缀名
                String fileExtName = uploadFileName.substring(uploadFileName.lastIndexOf(".") + 1);
                /*
                    如果文件后缀名fileExtName不是我们所需要的
                    就直接return，不处理，告诉用户文件类型不对
                */

                //可以使用UUID(唯一识别的通用码),保证文件名唯一
                //UUID.randomUUID，随机生一个唯一识别的通用码
                System.out.println("=====>文件信息【文件名：" + fileName + "文件类型：" + fileExtName + "】");

                //可以使用UUID(唯一通用识别码)来保证文件名的统一
                String uuidFileName = UUID.randomUUID().toString();


                //=======================保存文件=========================//
                //获得文件上传的流
                InputStream inputStream = fileItem.getInputStream();
                String newFilePath = uploadPath + "\\" + uuidFileName + "." + fileExtName;
                File newFile = new File(newFilePath);

                if (newFile.createNewFile()) {
                    //创建一个文件输出流
                    FileOutputStream fos = new FileOutputStream(newFile);
                    //创建一个缓冲区
                    byte[] buffer = new byte[1024 * 1024];
                    //判断是否读取完毕
                    int len = 0;
                    //如果大于0，说明还存在数据
                    while ((len = inputStream.read(buffer)) > 0) {
                        fos.write(buffer, 0, len);
                    }

                    //关闭流
                    fos.close();
                    inputStream.close();
                } else {
                    throw new Exception("File already exist: " + newFilePath);
                }
                // 上传成功，清除临时文件(虽然会自动删除，但是使用此方法可以更早删除)
                fileItem.delete();
            }
        }
        return true;
    }


    /**
     * 判断上传的表单是普通表单还是带文件的表单，是返回true,否返回false；
     *
     * @param request: http请求
     * @return boolean
     * @author He Peng
     * @date 2021/8/23 15:25
     */
    public static boolean isMultipart(HttpServletRequest request) {
        return ServletFileUpload.isMultipartContent(request);
    }

    /**
     * 获取WEB项目下的WEB-INF目录
     * （idea中的out文件夹下）
     *
     * @return java.lang.String
     * @author He Peng
     * @date 2021/8/22 17:02
     */
    private static String getWebInfPath() {
        // 获得当前线程所在路径
        String path = Thread.currentThread().getContextClassLoader().getResource("").toString();
        // 去掉路径中的classes文件夹
        path = path.substring(0, path.indexOf("classes"));
        // 将/换成\
        path = path.replace('/', '\\');
        //去掉file:\
        path = path.replace("file:\\", "");

        return path;
    }

    /**
     * 返回当前web应用程序的临时目录，由servlet容器提供。
     *
     * @param servletContext: 此web应用的servlet context
     * @return java.io.File
     * @author He Peng
     * @date 2021/8/23 11:07
     */
    public static File getTmpDir(ServletContext servletContext) {
        Assert.notNull(servletContext, "ServletContext must not be null");
        return (File) servletContext.getAttribute(TEMP_DIR_CONTEXT_ATTRIBUTE);
    }

    /**
     * 解析http request 变成 MultipartFile
     *
     * @param req:
     * @return java.util.List<com.bluedot.www.framework.mvc.servlet.http.MultipartFile>
     * @author He Peng
     * @date 2021/8/23 16:15
     */
    public List<MultipartFile> parseRequestToMultipartFile(HttpServletRequest req) {

        if (!isMultipart(req)) {
            return null;
        }

        // 临时路径，如果上传的文件超过预期的大小，我们将它存放到一个临时目录中
        // 此临时路径由servlet容器提供
        File file = getTmpDir(req.getServletContext());

        //1、创建DiskFileItemFactory对象，处理文件上传路径或限制文件大小
        DiskFileItemFactory factory = new DiskFileItemFactory();
        //通过这个工厂设置一个缓冲区，当上传的文件大小大于缓冲区的时候，将它放到临时文件中；
        //缓冲区大小为1M
        factory.setSizeThreshold(sizeThreshold);
        factory.setRepository(file);


        ServletFileUpload upload = new ServletFileUpload(factory);
        //处理乱码问题
        upload.setHeaderEncoding("UTF-8");
        //设置单个文件的最大值
        upload.setFileSizeMax(fileSizeMax);
        //设置总共能够上传文件的大小
        //1024 = 1kb * 1024 = 1M * 10 = 10M
        upload.setSizeMax(sizeMax);

        //把前端的请求解析，封装成一个FileItem对象
        List<FileItem> fileItems = null;
        try {
            fileItems = upload.parseRequest(req);
        } catch (FileUploadException e) {
            e.printStackTrace();
        }

        List<MultipartFile> multipartFiles = new ArrayList<>();
        Assert.notNull(fileItems, "request请求中没有文件");
        for (FileItem fileItem : fileItems) {
            CommonsMultipartFile multipartFile = new CommonsMultipartFile(fileItem);
            multipartFiles.add(multipartFile);
        }

        return multipartFiles;
    }

    public Integer getSizeThreshold() {
        return sizeThreshold;
    }

    public void setSizeThreshold(Integer sizeThreshold) {
        if (fileSizeMax > 0 && fileSizeMax < MAX_SIZE / 10) {
            this.sizeThreshold = sizeThreshold;
        }
    }

    public Integer getFileSizeMax() {
        return fileSizeMax;
    }

    public void setFileSizeMax(Integer fileSizeMax) {
        if (fileSizeMax > 0 && fileSizeMax < MAX_SIZE) {
            this.fileSizeMax = fileSizeMax;
        }
    }

    public Integer getSizeMax() {
        return sizeMax;
    }

    public void setSizeMax(Integer sizeMax) {
        if (fileSizeMax > 0 && fileSizeMax < MAX_SIZE) {
            this.sizeMax = sizeMax;
        }
    }
}
